package se.rende.myrss;

import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLConnection;
import java.net.URLEncoder;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Locale;

import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.xml.sax.InputSource;

import se.rende.myrss.data.DataService;
import se.rende.myrss.data.Filter;

import com.sun.syndication.feed.synd.SyndEntry;
import com.sun.syndication.feed.synd.SyndFeed;
import com.sun.syndication.feed.synd.SyndFeedImpl;
import com.sun.syndication.io.FeedException;
import com.sun.syndication.io.SyndFeedInput;
import com.sun.syndication.io.SyndFeedOutput;

/**
 * Downloads from each RS url supplied in a ch parameter, and returns an aggregated RSS 2.0 result. 
 * In each result item, source holds the site title. All titles are included URL encoded in the aggregated feed title separated by &.
 * If there are a since parameter for each ch parameter, only items later than the date in since is returned (date syntax is "EEE MMM dd HH:mm:ss z yyyy" with US locale).
 */
@SuppressWarnings("serial")
public class RssServlet extends HttpServlet {
	private static final SimpleDateFormat headerDateFormat = new SimpleDateFormat("EEE MMM dd HH:mm:ss z yyyy", Locale.US);
	private DataService dataService;
	
	@Override
	public void init(ServletConfig config) throws ServletException {
		dataService = (DataService) config.getServletContext().getAttribute("data");
	}

	public void doGet(HttpServletRequest req, HttpServletResponse resp)
			throws IOException {
		System.out.println("req.getQueryString()=" + req.getQueryString());
		String ifModifiedSince = req.getHeader("If-Modified-Since");
		
		// get all channel params, and parallel since dates
		String[] urls = req.getParameterValues("ch");
		String[] sinces = req.getParameterValues("since");
		if (urls != null && urls.length > 0) {
			resp.setContentType("text/plain");
			resp.setCharacterEncoding("utf-8");
			
			SyndFeed aggrFeed = new SyndFeedImpl();
			aggrFeed.setFeedType("rss_2.0_withSource");

			aggrFeed.setDescription("Anonymous Aggregated Feed");
			aggrFeed.setAuthor("anonymous");
			aggrFeed.setLink("http://myrss-server.appspot.com");
			StringBuilder channelTitleBuilder = new StringBuilder();

			List aggrEntries = new ArrayList();
			aggrFeed.setEntries(aggrEntries);
			
			for (int i = 0; i < urls.length; i++) {
				String url = urls[i];
				try {
					URL feedUrl = new URL(url);
					URLConnection urlConnection = feedUrl.openConnection();
					urlConnection.setDoOutput(true);
					if (urls != null && sinces != null && urls.length == sinces.length) {
						ifModifiedSince = sinces[i];
					}
					if (ifModifiedSince == null) {
						ifModifiedSince = headerDateFormat.format(new Date(0L));
					}
					urlConnection.setRequestProperty("If-Modified-Since", ifModifiedSince);
					
					if (urlConnection instanceof HttpURLConnection) {
						HttpURLConnection httpConnection = (HttpURLConnection) urlConnection;
						int code = httpConnection.getResponseCode();
						if (code == 200) {
							BufferedInputStream is = new BufferedInputStream(httpConnection.getInputStream());
							skipSpace(is);
							SyndFeedInput input = new SyndFeedInput();
							SyndFeed feed = input.build(new InputSource(is));
							is.close();
							
							if (channelTitleBuilder.length() > 0) {
								channelTitleBuilder.append("&");
							}
							channelTitleBuilder.append(URLEncoder.encode(feed.getTitle(), "utf-8"));
							
							List entries = feed.getEntries();
							for (Object entry : entries) {
								SyndEntry syndEntry = (SyndEntry) entry;
								syndEntry.setSource(feed);
								filterEntry(url, syndEntry);
								if (ifModifiedSince == null 
										|| syndEntry.getPublishedDate().after(headerDateFormat.parse(ifModifiedSince))) {
									aggrEntries.add(entry);
								}
							}
						}
					}
				} catch (IllegalArgumentException e) { 
					throw new IOException("can't serve rss", e);
				} catch (FeedException e) {
					throw new IOException("can't serve rss", e);
				} catch (ParseException e) {
					throw new IOException("can't serve rss", e);
				}
			}
			aggrFeed.setTitle(channelTitleBuilder.toString());
			SyndFeedOutput output = new SyndFeedOutput();
			ServletOutputStream os = resp.getOutputStream();
			PrintWriter writer = new PrintWriter(new OutputStreamWriter(os,
					"utf-8"));
			
			try {
				output.output(aggrFeed, writer);
			} catch (FeedException e) {
				throw new IOException("can't serve rss", e);			}
			writer.close();
		}
	}

	/**
	 * Skip chars for is until a '<'
	 * @param is
	 * @throws IOException
	 */
	private static void skipSpace(InputStream is) throws IOException {
		while (true) {
			is.mark(1);
			int ch = is.read();
			if (ch <= 0 || ch == '<') {
				is.reset();
				break;
			}
			
		}
	}

	private void filterEntry(String url, SyndEntry syndEntry) {
		String s = syndEntry.getDescription().getValue();
		List<Filter> filters = dataService.getFiltersByUrl(url);
		for (Filter filter : filters) {
			s = filter.execute(s, syndEntry);
		}		
		syndEntry.getDescription().setValue(s);
	}

}
